package com.utils

import com.*
import com.boge.utils.SystemUtils
import java.net.URI
import java.nio.ByteBuffer
import java.nio.channels.Channels
import java.nio.channels.FileChannel
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.concurrent.CompletableFuture
import kotlin.io.path.*

/**
 *
 * 转换webp
 *
 * [学习地址](https://developers.google.com/speed/webp/download?hl=zh-cn)
 * [下载代码库](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html)
 * [libwebp-1.3.2.tar.gz](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.3.2.tar.gz)
 * [libwebp-1.3.2-windows-x64.zip](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.3.2-windows-x64.zip)
 *
 * 列表：
 * ```
 * libwebp-1.3.2-ios-framework.tar.gz                          2023-10-10T23:14:58Z    20.12MiB
 * libwebp-1.3.2-ios-framework.tar.gz.asc                      2023-10-10T23:14:56Z        833B
 * libwebp-1.3.2-linux-aarch64.tar.gz                          2023-09-14T01:51:00Z     9.02MiB
 * libwebp-1.3.2-linux-aarch64.tar.gz.asc                      2023-09-14T01:50:55Z        833B
 * libwebp-1.3.2-linux-x86-64.tar.gz                           2023-09-14T01:51:00Z    11.21MiB
 * libwebp-1.3.2-linux-x86-64.tar.gz.asc                       2023-09-14T01:50:55Z        833B
 * libwebp-1.3.2-mac-arm64.tar.gz                              2023-09-14T01:50:58Z     4.84MiB
 * libwebp-1.3.2-mac-arm64.tar.gz.asc                          2023-09-14T01:50:56Z        833B
 * libwebp-1.3.2-mac-x86-64.tar.gz                             2023-09-14T01:50:58Z     5.96MiB
 * libwebp-1.3.2-mac-x86-64.tar.gz.asc                         2023-09-14T01:50:56Z        833B
 * libwebp-1.3.2-windows-x64-no-wic.zip                        2023-09-14T01:50:59Z     4.88MiB
 * libwebp-1.3.2-windows-x64-no-wic.zip.asc                    2023-09-14T01:50:56Z        833B
 * libwebp-1.3.2-windows-x64.zip                               2023-09-14T01:50:59Z     3.66MiB
 * libwebp-1.3.2-windows-x64.zip.asc                           2023-09-14T01:50:56Z        833B
 * libwebp-1.3.2.tar.gz                                        2023-09-14T01:50:57Z     3.97MiB
 * libwebp-1.3.2.tar.gz.asc
 *
 * ```
 *
 */
object WebpUtils {

    private const val downloadUrl = "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/"
    private val cwebpPath: Path by lazy {
        SystemUtils.runPath.resolve("webp/cwebp${if (SystemUtils.isWin) ".exe" else ""}").normalize()
    }
    private val dwebpPath: Path by lazy {
        SystemUtils.runPath.resolve("webp/dwebp${if (SystemUtils.isWin) ".exe" else ""}").normalize()
    }
    private val gwebpPath: Path by lazy {
        SystemUtils.runPath.resolve("webp/gwebp${if (SystemUtils.isWin) ".exe" else ""}").normalize()
    }
    private val webpmuxPath: Path by lazy {
        SystemUtils.runPath.resolve("webp/webpmux${if (SystemUtils.isWin) ".exe" else ""}").normalize()
    }

    @OptIn(ExperimentalPathApi::class)
    @Synchronized
    fun isVerify(path: Path? = null): Path {
        val verifyPath = path ?: cwebpPath
        if (!verifyPath.exists()) {
            val name = System.getProperty("os.name")
            val arch = System.getProperty("os.arch")
            val systemName = "libwebp-1.3.2-" + when {
                name.contains("mac", true) -> {
                    if (arch.contains("arm64", true))
                        "mac-arm64.tar.gz"
                    else "mac-x86-64.tar.gz"
                }

                name.contains("ios", true) -> {
                    "ios-framework.tar.gz"
                }

                name.contains("linux", true) -> {
                    if (arch.contains("aarch64", true))
                        "linux-x86-64.tar.gz"
                    else "linux-aarch64.tar.gz"
                }

                else -> "windows-x64.zip"
            }

            val url = "$downloadUrl${systemName}"
            logger.info("不存在执行文件，从网络中获取: $url")
            val input = URI.create(url).toURL().openStream()
            val readableByteChannel = Channels.newChannel(input)
            // 定义缓冲区
            val buffer = ByteBuffer.allocateDirect(1024) // 可以调整缓冲区大小
            val outPath = Path("${SystemUtils.dir}/$systemName")
            val fileChannel = FileChannel.open(
                outPath,
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE,
                StandardOpenOption.APPEND,
            )
            while (true) {
                // 清空缓冲区
                buffer.clear()
                // 从网络通道中读取数据到缓冲区
                val nBytesRead = readableByteChannel.read(buffer)
                // 如果没有更多数据可读，则跳出循环
                if (nBytesRead == -1) {
                    break
                }
                // 切换缓冲区为读模式
                buffer.flip()
                // 将缓冲区中的数据写入本地文件通道
                fileChannel.write(buffer)
                // 如果缓冲区还有剩余未写完的数据，则需要再次翻转
                if (buffer.hasRemaining()) {
                    buffer.compact()
                } else {
                    buffer.clear()
                }
            }

            Utils.unZip(outPath, SystemUtils.runPath.resolve("webp"), "${outPath.nameWithoutExtension}/bin")

            outPath.deleteRecursively()

        }
        return verifyPath
    }

    fun runPngToWebp(convert: List<ConvertWebp>) {
        isVerify()
        val futures = mutableListOf<CompletableFuture<Unit>>()
        for (convertWebp in convert) {
            val future = CompletableFuture.supplyAsync({
                toWebp(convertWebp)
            }, Utils.executors)
            futures.add(future)
        }
        CompletableFuture.allOf(*futures.toTypedArray()).join()
    }

    @OptIn(ExperimentalPathApi::class)
    fun toWebp(convertWebp: ConvertWebp) {
        var filePath = convertWebp.filePath
        if (convertWebp.compress) {
            val compressData = CompressData(filePath, outDir = Utils.tempPath)
            filePath = compressData.getCompressPath()
            CompressUtils.compressImage(compressData)
        }
        cwebp(
            filePath.pathString,
            convertWebp.getConvertPath().pathString,
            mapOf("-q" to convertWebp.webpQuality.toString())
        )
        if (convertWebp.compress) filePath.deleteRecursively()
        else if (convertWebp.removeSource) convertWebp.filePath.also {
            it.exists().ok {
                it.deleteRecursively()
            }
        }
    }

    /**
     * now convert image to .webp format
     * @param inputImage input image(.jpeg, .pnp ....)
     * @param outputImage output image .webp
     * @param option options and quality,it should be given between 0 to 100
     * @param logging
     */
    fun cwebp(inputImage: String, outputImage: String, option: Map<String, String>, logging: String = "-quiet") {
        //enwebp() return which platform webp library should be used for conversion
        exec(
            "cwebp",
            arrayOf(*option.flatMap { listOf(it.key, it.value) }.toTypedArray(), inputImage, "-o", outputImage, logging)
        )
        logger.info("cwebp $inputImage to $outputImage")
    }

    fun exec(execFileName: String, array: Array<String>) {
        val runPath = when (execFileName) {
            "dwebp" -> isVerify(dwebpPath)
            "gwebp" -> isVerify(gwebpPath)
            "webpmux" -> isVerify(webpmuxPath)
            else -> isVerify()
        }
        val path = runPath.absolutePathString()
        val execCmd = arrayOf(path, *array)
        logger.info("执行cmd命令： ${execCmd.joinToString(" ")}")

        val process = Runtime.getRuntime().exec(execCmd)
        val code = process.waitFor()
        if (code != 0) logger.error(process.errorReader().readText())
    }

    /******************************************************* dwebp *****************************************************/

    //now convert .webp to other image format
    fun dwebp(inputImage: String, outputImage: String, option: Map<String, String>, logging: String = "-quiet") {
        // inputImage: input image .webp
        //outputImage: output image(.jpeg, .pnp ....)
        //option: options and quality,it should be given between 0 to 100
        //dewebp() return which platform webp library should be used for conversion
        exec(
            "dwebp",
            arrayOf(inputImage, *option.flatMap { listOf(it.key, it.value) }.toTypedArray(), outputImage, logging)
        )
        logger.info("dwebp $inputImage to $outputImage")

    }

    /******************************************************* gif2webp *****************************************************/

    //now convert .gif image to .webp format
    fun gwebp(inputImage: String, outputImage: String, option: Map<String, String>, logging: String = "-quiet") {

        // inputImage: input image(.jpeg, .pnp ....)
        //outputImage: /output image .webp
        //option: options and quality,it should be given between 0 to 100

        exec(
            "gwebp",
            arrayOf(*option.flatMap { listOf(it.key, it.value) }.toTypedArray(), inputImage, "-o", outputImage, logging)
        )
        logger.info("gwebp $inputImage to $outputImage")

    }

    /******************************************************* webpmux *****************************************************/

    // Add ICC profile,XMP metadata and EXIF metadata
    fun webpmuxAdd(
        inputImage: String,
        outputImage: String,
        icc_profile: String,
        option: Map<String, String>,
        logging: String = "-quiet"
    ) {

        // inputImage: input image(.webp)
        //outputImage: output image .webp
        //icc_profile: icc profile
        //option: get or set option (icc,xmp,exif)

        exec(
            "webpmux",
            arrayOf(
                "-set",
                *option.flatMap { listOf(it.key, it.value) }.toTypedArray(),
                icc_profile,
                inputImage,
                "-o",
                outputImage,
                logging
            )
        )
        logger.info("webpmux $inputImage to $outputImage")

    }

    // Extract ICC profile,XMP metadata and EXIF metadata

    fun webpmuxExtract(
        inputImage: String,
        icc_profile: String,
        option: Map<String, String>,
        logging: String = "-quiet"
    ) {

        // inputImage: input image(.webp)
        //icc_profile: icc profile
        exec(
            "webpmux",
            arrayOf(
                "-get",
                *option.flatMap { listOf(it.key, it.value) }.toTypedArray(),
                inputImage,
                "-o",
                icc_profile,
                logging
            )
        )
        logger.info("webpmux $inputImage to $icc_profile")
    }

    // Strip ICC profile,XMP metadata and EXIF metadata

    fun webpmuxStrip(inputImage: String, outputImage: String, option: Map<String, String>, logging: String = "-quiet") {

        // inputImage: input image(.webp)
        //outputImage: output image .webp

        exec(
            "webpmux",
            arrayOf(
                "-strip",
                *option.flatMap { listOf(it.key, it.value) }.toTypedArray(),
                inputImage,
                "-o",
                outputImage,
                logging
            )
        )
        logger.info("webpmux $inputImage to $outputImage")

    }

    // Create an animated WebP file from Webp images

    fun webpmuxAnimate(
        inputImage: List<IMG>,
        outputImage: String,
        loop: String,
        bgcolor: String,
        logging: String = "-quiet"
    ) {

        // inputImage: array of image(.webp)
        //outputImage: animatedimage .webp
        //loop:Loop the frames n number of times
        //bgcolor: Background color of the canvas

        var files = "-frame ${inputImage[0].path} ${inputImage[0].offset}"
        for (i in 1..<inputImage.size) {
            files = "$files -frame ${inputImage[i].path} ${inputImage[i].offset}"
        }
        exec("webpmux", arrayOf(files, "-loop", loop, "-bgcolor", bgcolor, "-o", outputImage, logging))
//        logger.info("webpmux $inputImage to $outputImage")
    }
    // Get the a frame from an animated WebP file

    fun webpmuxGetframe(inputImage: String, outputImage: String, frameNumber: String, logging: String = "-quiet") {

        // inputImage: input image(.webp)
        //outputImage: output image .webp
        //frameNumber: frame number

        exec("webpmux", arrayOf("-get", "frame", frameNumber, inputImage, "-o", outputImage, logging))
        logger.info("webpmux $inputImage to $outputImage")
    }

}

data class IMG(
    val path: String,
    val offset: String
)